import { DeviceInputKey, InputStatus } from 'babylon-lib/preset/device-input/device-information';
import { CharacterController } from './character-controller';
import { DeviceInputPreset } from 'babylon-lib/preset/device-input/device-input-preset';
import { VirtualJoystick } from './virtual-joystick';
import { PointerInfo } from '@babylonjs/core/Events/pointerEvents';
import { Vector2, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { AnimationFrame, Utils } from 'babylon-lib/index';


class CharacterControllerInput {

    public static clickThreshold = 100;
    public static doubleClickInterval = 200;

    private static _jumpReq = {};
    private static _jumpDuration = 500;

    public static attach(controller: CharacterController): void {
        if (Utils.isPC()) {
            this.setKeyInput(controller);
        } else {
            this.setTouchInput(controller);
            this.setVirtualJoystickInput(controller);
        }
    }

    private static setVirtualJoystickInput(controller: CharacterController): void {
        VirtualJoystick.initialize(controller.stage);
    }

    private static setTouchInput(controller: CharacterController): void {
        const scope = CharacterControllerInput;
        const beginPointer = new Vector2();
        const endPointer = new Vector2();
        const camera = controller.camera;
        const stopSensibility = 1e6;
        const moveSensibility = 1e3;
        let jumpCount = 0;
        let isDown = false;
        let isLeft = false;
        let isPress = false;
        const dir = new Vector3();
        let jumpStart = false;
        const jumpReq = {};
        let isClick: boolean = true;
        DeviceInputPreset.onInput((e?: PointerInfo) => {
            if (!e) return;
            if (isDown) return;
            isClick = true;
            isDown = true;
            const canvas = e.event.target as HTMLCanvasElement;
            beginPointer.set(e.event.clientX, e.event.clientY);
            isLeft = beginPointer.x < canvas.clientWidth * 0.5;
            if (isLeft) {
                camera.angularSensibilityX = camera.angularSensibilityY = stopSensibility;
                VirtualJoystick.update(e.event.clientX, e.event.clientY);
            } else {
                camera.angularSensibilityX = camera.angularSensibilityY = moveSensibility;
            }

            if (!jumpStart) {
                jumpStart = true;
                jumpCount = 0;
            } else {
                jumpCount++;
            }
        }, InputStatus.down, DeviceInputKey.pointer);

        DeviceInputPreset.onInput((e?: PointerInfo) => {
            if (!e) return;
            if (!isDown) return;
            endPointer.set(e.event.clientX, e.event.clientY);
            if (isClick) isClick = Vector2.DistanceSquared(beginPointer, endPointer) > scope.clickThreshold;
            if (isLeft) {
                if (!isClick) {
                    dir.set(endPointer.x - beginPointer.x, 0, endPointer.y - beginPointer.y);
                    controller.direction = dir.normalize();
                    VirtualJoystick.update(endPointer.x, endPointer.y);
                    VirtualJoystick.active = true;
                }
            }

            if (isClick) {
                jumpStart = false;
            }
        }, InputStatus.move, DeviceInputKey.pointer);

        DeviceInputPreset.onInput(() => {
            if (!isDown) return;
            isDown = false;
            if (isLeft) {
                isLeft = false;
                camera.angularSensibilityX = camera.angularSensibilityY = moveSensibility;
                dir.set(0, 0, 0);
                controller.direction = dir;
                VirtualJoystick.update();
                VirtualJoystick.active = false;
            }

            if (jumpStart) {
                AnimationFrame.throttleByTime(jumpReq, scope.doubleClickInterval, () => {
                    controller.jump = jumpCount > 0;
                    jumpStart = false;
                    AnimationFrame.throttleByTime(jumpReq, scope._jumpDuration, () => {
                        controller.jump = false;
                    }, true);
                });
            }
        }, InputStatus.up, DeviceInputKey.pointer);

        DeviceInputPreset.onInput((e?: PointerInfo) => {
            if (!e) return;
            if (isPress) return;
            isPress = true;
            const canvas = e?.event.target as HTMLCanvasElement;
            if (e.event.clientY < canvas.clientHeight * 0.5) {
                controller.up = true;
            } else {
                controller.down = true;
            }
        }, InputStatus.press, DeviceInputKey.pointer);
        DeviceInputPreset.onInput(() => {
            if (!isPress) return;
            isPress = false;
            controller.up = false;
            controller.down = false;
        }, InputStatus.up, DeviceInputKey.pointer);
    }

    private static setKeyInput(controller: CharacterController): void {
        const scope = CharacterControllerInput;
        const forwardKey = [DeviceInputKey.keyboardW];
        DeviceInputPreset.onInput((e) => {
            controller.forward = true;
        }, InputStatus.down, ...forwardKey);
        DeviceInputPreset.onInput((e) => {
            controller.forward = false;
        }, InputStatus.up, ...forwardKey)

        const backwardKey = [DeviceInputKey.keyboardS];
        DeviceInputPreset.onInput((e) => {
            controller.backward = true;
        }, InputStatus.down, ...backwardKey);
        DeviceInputPreset.onInput((e) => {
            controller.backward = false;
        }, InputStatus.up, ...backwardKey)

        const leftKey = [DeviceInputKey.keyboardA];
        DeviceInputPreset.onInput((e) => {
            controller.left = true;
        }, InputStatus.down, ...leftKey);
        DeviceInputPreset.onInput((e) => {
            controller.left = false;
        }, InputStatus.up, ...leftKey)

        const rightKey = [DeviceInputKey.keyboardD];
        DeviceInputPreset.onInput((e) => {
            controller.right = true;
        }, InputStatus.down, ...rightKey);
        DeviceInputPreset.onInput((e) => {
            controller.right = false;
        }, InputStatus.up, ...rightKey)

        const runKey = [DeviceInputKey.keyboardShift];
        DeviceInputPreset.onInput((e) => {
            controller.run = true;
        }, InputStatus.down, ...runKey);
        DeviceInputPreset.onInput(() => {
            controller.run = false;
        }, InputStatus.up, ...runKey)

        const jumpKey = [DeviceInputKey.keyboardSpace];
        DeviceInputPreset.onInput((e) => {
            if (AnimationFrame.isRunning(scope._jumpReq)) return;
            controller.jump = true;
            AnimationFrame.throttleByTime(scope._jumpReq, scope._jumpDuration, () => {
                controller.jump = false;
            }, true);
        }, InputStatus.down, ...jumpKey);

        const upKey = [DeviceInputKey.keyboardQ];
        DeviceInputPreset.onInput((e) => {
            controller.up = true;
        }, InputStatus.down, ...upKey);
        DeviceInputPreset.onInput((e) => {
            controller.up = false;
        }, InputStatus.up, ...upKey);

        const downKey = [DeviceInputKey.keyboardE];
        DeviceInputPreset.onInput((e) => {
            controller.down = true;
        }, InputStatus.down, ...downKey);
        DeviceInputPreset.onInput((e) => {
            controller.down = false;
        }, InputStatus.up, ...downKey);
    }

}


export { CharacterControllerInput };